﻿using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using DesignPatternToolkit.Core;

namespace DesignPatternToolkit.Patterns.State
{
    public class StateBuilder : PatternBuilder
    {
        private CodeTypeReference _contextClass;
        private CodeTypeReference _abstractState;

        public StateBuilder()
        {
            Configuration = new StateConfiguration();

            _contextClass = new CodeTypeReference(StateConfiguration.ContextClass);
            _abstractState = new CodeTypeReference(StateConfiguration.AbstractStateClass);
        }

        private StateConfiguration StateConfiguration
        {
            get { return Configuration as StateConfiguration; }
        }

        protected override List<CodeNamespace> CreateClassesCore()
        {
            List<CodeNamespace> result = new List<CodeNamespace>();

            CodeNamespace abstractState = CreateAbstractStateClass();
            CodeNamespace contextClass = CreateContextClass();
            List<CodeNamespace> stateClasses = CreateStateClasses();

            result.Add(abstractState);
            result.Add(contextClass);
            result.AddRange(stateClasses);

            return result;
        }

        private List<CodeNamespace> CreateStateClasses()
        {
            List<CodeNamespace> result = new List<CodeNamespace>();

            foreach (string concreteState in StateConfiguration.ConcreteStates)
            {
                result.Add(CreateStateClass(concreteState));
            }

            return result;
        }

        private CodeNamespace CreateStateClass(string concreteStateName)
        {
            CodeNamespace nameSpace = new CodeNamespace(Configuration.Namespace);

            CodeTypeDeclaration concreteState = new CodeTypeDeclaration(concreteStateName);
            concreteState.BaseTypes.Add(_abstractState);
            concreteState.Attributes = MemberAttributes.Public;
            nameSpace.Types.Add(concreteState);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = StateConfiguration.AbstractMethod;
            method.Attributes = MemberAttributes.Public | MemberAttributes.Override; // !!! Override base class method  !!!
            CodeParameterDeclarationExpression contextParameter = new CodeParameterDeclarationExpression(_contextClass, "context");
            method.Parameters.Add(contextParameter);
            // include comment telling the user to implement code. Usually MS starts throwing NotImplementedExceptions.
            CodeCommentStatement commentToDo = new CodeCommentStatement();
            commentToDo.Comment = new CodeComment("Please implement some funky stuff here.");
            method.Statements.Add(commentToDo);

            concreteState.Members.Add(method);

            return nameSpace;
        }

        private CodeNamespace CreateContextClass()
        {
            CodeNamespace nameSpace = new CodeNamespace(Configuration.Namespace);

            /* Create the Context class */
            CodeTypeDeclaration contextClass = new CodeTypeDeclaration(StateConfiguration.ContextClass);
            contextClass.TypeAttributes = System.Reflection.TypeAttributes.Public;
            nameSpace.Types.Add(contextClass);
            _contextClass = new CodeTypeReference(contextClass.Name);

            /* Create private field of type abstract State. */
            CodeMemberField stateField = new CodeMemberField(_abstractState, "_state");
            stateField.Attributes = MemberAttributes.Private;
            contextClass.Members.Add(stateField);

            // we need a reference to this field later.
            CodeFieldReferenceExpression stateFieldRef = new CodeFieldReferenceExpression();
            stateFieldRef.FieldName = stateField.Name;

            /* Create the constructor that takes a State object. */
            CodeConstructor constructor = new CodeConstructor();
            CodeParameterDeclarationExpression constrParameter = new CodeParameterDeclarationExpression(_abstractState, "state");
            constructor.Parameters.Add(constrParameter);
            /* Assign the constructor parameter to the private class field. */
            CodeAssignStatement assignToPrivate = new CodeAssignStatement();
            assignToPrivate.Left = stateFieldRef;
            assignToPrivate.Right = new CodeSnippetExpression("state") ; //Dirty!!!
            // add the assign statement to the constructor.
            constructor.Statements.Add(assignToPrivate);
            contextClass.Members.Add(constructor);

            /* Create a get and set State property */
            CodeMemberProperty stateProperty = new CodeMemberProperty();
            stateProperty.Name = "State";
            stateProperty.Type = _abstractState;
            stateProperty.HasGet = true;
            stateProperty.HasSet = true;
            // create the get; returns the private state field.
            stateProperty.GetStatements.Add(new CodeMethodReturnStatement(stateFieldRef));
            // create the set; assign the value to the private state field.
            CodeSnippetExpression valueExpression = new CodeSnippetExpression("value");
            stateProperty.SetStatements.Add(new CodeAssignStatement(stateFieldRef, valueExpression));

            contextClass.Members.Add(stateProperty);

            /* Finally, implement the method to invoke the state action. */
            CodeMemberMethod contextMethod = new CodeMemberMethod();
            contextMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final; // include Final, else method = virtual.
            contextMethod.Name = StateConfiguration.ContextRequestMethod;
            // we need to call the private field's 'Handle' method with a ref to this context class:
            // _state.Handle(this);
            CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression();
            methodInvoke.Method = new CodeMethodReferenceExpression();
            methodInvoke.Method.MethodName = StateConfiguration.AbstractMethod;
            methodInvoke.Method.TargetObject = stateFieldRef;
            methodInvoke.Parameters.Add(new CodeSnippetExpression("this"));
            contextMethod.Statements.Add(methodInvoke);

            contextClass.Members.Add(contextMethod);

            return nameSpace;
        }

        private CodeNamespace CreateAbstractStateClass()
        {
            CodeNamespace nameSpace = new CodeNamespace(Configuration.Namespace);

            /* Create the abstract state class. */
            CodeTypeDeclaration abstractState = new CodeTypeDeclaration(StateConfiguration.AbstractStateClass);
            abstractState.TypeAttributes = System.Reflection.TypeAttributes.Abstract | System.Reflection.TypeAttributes.Public;
            nameSpace.Types.Add(abstractState);
            _abstractState = new CodeTypeReference(abstractState.Name);

            /* Create the 'Handle(Context context) method. */
            CodeMemberMethod abstractMethod = new CodeMemberMethod();
            abstractMethod.Name = StateConfiguration.AbstractMethod;
            abstractMethod.Attributes = MemberAttributes.Abstract | MemberAttributes.Public;
            CodeParameterDeclarationExpression contextParameter = new CodeParameterDeclarationExpression();
            _contextClass = new CodeTypeReference(StateConfiguration.ContextClass);
            contextParameter.Type = _contextClass;
            contextParameter.Name = "context";
            abstractMethod.Parameters.Add(contextParameter);

            abstractState.Members.Add(abstractMethod);

            return nameSpace;
        }

        public override string PatternName
        {
            get
            {
                return "State";
            }
        }
    }
}
